/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

@file:JvmName("AaptV2CommandBuilder")

package com.android.builder.internal.aapt.v2

import com.android.SdkConstants
import com.android.builder.internal.aapt.AaptException
import com.android.builder.internal.aapt.AaptPackageConfig
import com.android.builder.internal.aapt.AaptUtils
import com.android.builder.internal.aapt.BlockingResourceLinker
import com.android.ide.common.resources.CompileResourceRequest
import com.android.utils.FileUtils
import com.google.common.base.Joiner
import com.google.common.base.Preconditions
import com.google.common.base.Strings
import com.google.common.collect.ImmutableList
import com.google.common.collect.Iterables
import com.google.common.collect.Lists
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.PrintWriter
import java.util.ArrayList
import java.util.Locale
import java.util.Objects

/**
 * Creates the command line used to compile a resource. See
 * [com.android.builder.internal.aapt.Aapt.compile].
 *
 * @return the command line arguments
 */
fun makeCompileCommand(request: CompileResourceRequest): ImmutableList<String> {
    val parameters = ImmutableList.Builder<String>()

    if (request.isPseudoLocalize) {
        parameters.add("--pseudo-localize")
    }

    if (!request.isPngCrunching) {
        // Only pass --no-crunch for png files and not for 9-patch files as that breaks them.
        val lowerName = request.inputFile.path.toLowerCase(Locale.US)
        if (lowerName.endsWith(SdkConstants.DOT_PNG)
                && !lowerName.endsWith(SdkConstants.DOT_9PNG)) {
            parameters.add("--no-crunch")
        }
    }

    if (request.partialRFile != null) {
        parameters.add("--output-text-symbols", request.partialRFile!!.absolutePath)
    }

    parameters.add("--legacy")
    parameters.add("-o", request.outputDirectory.absolutePath)
    parameters.add(request.inputFile.absolutePath)

    return parameters.build()
}

/**
 * Creates the command line used to link the package.
 *
 *
 * See [BlockingResourceLinker.link].
 *
 * @param config see above
 * @return the command line arguments
 * @throws AaptException failed to build the command line
 */
@Throws(AaptException::class)
fun makeLinkCommand(config: AaptPackageConfig): ImmutableList<String> {
    val builder = ImmutableList.builder<String>()

    if (config.verbose) {
        builder.add("-v")
    }

    if (config.generateProtos) {
        builder.add("--proto-format")
    }

    // inputs
    builder.add("-I", Preconditions.checkNotNull(config.androidJarPath))

    config.imports.forEach { file -> builder.add("-I", file.absolutePath) }

    val manifestFile = config.manifestFile
    Preconditions.checkNotNull(manifestFile)
    builder.add("--manifest", manifestFile.absolutePath)

    val resourceOutputApk: File
    resourceOutputApk = config.resourceOutputApk ?: try {
        val tmpOutput = File.createTempFile("aapt-", "-out")
        tmpOutput.deleteOnExit()
        tmpOutput
    } catch (e: IOException) {
        throw AaptException("No output apk defined and failed to create tmp file", e)
    }
    builder.add("-o", resourceOutputApk.absolutePath)

    if (!config.resourceDirs.isEmpty()) {
        try {
            if (config.isListResourceFiles()) {
                // AAPT2 only accepts individual files passed to the -R flag. In order to not
                // pass every single resource file, instead create a temporary file containing a
                // list of resource files and pass it as the only -R argument.
                val resourceListFile = File(
                        config.intermediateDir!!,
                        "resources-list-for-" + resourceOutputApk.name + ".txt"
                )

                // Resources list could have changed since last run.
                FileUtils.deleteIfExists(resourceListFile)
                for (dir in config.resourceDirs) {
                    FileOutputStream(resourceListFile).use { fos ->
                        PrintWriter(fos).use { pw ->
                            dir.listFiles()
                                .filter { it.isFile }
                                .forEach { pw.print(it.absolutePath + " ") }
                        }
                    }
                }
                builder.add("-R", "@" + resourceListFile.absolutePath)
            } else {
                for (dir in config.resourceDirs) {
                    dir.listFiles()
                        .filter { it.isFile }
                        .forEach { builder.add("-R", it.absolutePath) }
                }
            }
        } catch (e: IOException) {
            throw AaptException(
                    "Failed to walk paths " +
                            Joiner.on(File.pathSeparatorChar).join(config.resourceDirs),
                    e
            )
        }

    }

    builder.add("--auto-add-overlay")

    // outputs
    if (config.sourceOutputDir != null) {
        builder.add("--java", config.sourceOutputDir.absolutePath)
    }

    if (config.proguardOutputFile != null) {
        builder.add("--proguard", config.proguardOutputFile.absolutePath)
    }

    if (config.mainDexListProguardOutputFile != null) {
        builder.add(
                "--proguard-main-dex",
                config.mainDexListProguardOutputFile.absolutePath
        )
    }

    if (config.splits != null) {
        for (split in config.splits) {
            val splitter = File.pathSeparator
            builder.add(
                    "--split",
                    resourceOutputApk.toString() + "_" + split + splitter + split
            )
        }
    }

    // options controlled by build variants
    if (!config.variantType.isTestComponent && config.customPackageForR != null) {
        builder.add("--custom-package", config.customPackageForR)
    }

    // bundle specific options
    var generateFinalIds = true
    if (config.variantType.isAar) {
        generateFinalIds = false
    }
    if (!generateFinalIds) {
        builder.add("--non-final-ids")
    }

    /*
     * Never compress apks.
     */
    builder.add("-0", "apk")

    /*
     * Add custom no-compress extensions.
     */
    val noCompressList = Objects.requireNonNull(config.options).noCompress
    if (noCompressList != null) {
        for (noCompress in noCompressList) {
            if (!Strings.isNullOrEmpty(noCompress)) {
                builder.add("-0", noCompress)
            } else {
                // Empty sting means 'do not compress any resources'.
                builder.add("--no-compress")
            }
        }
    }

    val additionalParameters = config.options.additionalParameters
    if (additionalParameters != null) {
        builder.addAll(additionalParameters)
    }

    val resourceConfigs = ArrayList(config.resourceConfigs)

    /*
     * Split the density and language resource configs, since starting in 21, the
     * density resource configs should be passed with --preferred-density to ensure packaging
     * of scalable resources when no resource for the preferred density is present.
     */
    val densityResourceConfigs = Lists.newArrayList(
            AaptUtils.getDensityResConfigs(resourceConfigs)
    )
    val otherResourceConfigs = Lists.newArrayList(
            AaptUtils.getNonDensityResConfigs(
                    resourceConfigs
            )
    )
    var preferredDensity = config.preferredDensity

    val densityResSplits = if (config.splits != null)
        AaptUtils.getDensityResConfigs(config.splits)
    else
        ImmutableList.of()

    if ((preferredDensity != null || densityResSplits.iterator().hasNext())
            && !densityResourceConfigs.isEmpty()) {
        throw AaptException(
                String.format(
                        "When using splits in tools 21 and above, "
                                + "resConfigs should not contain any densities. Right now, it "
                                + "contains \"%1\$s\"\nSuggestion: remove these from resConfigs"
                                + " from build.gradle",
                        Joiner.on("\",\"").join(densityResourceConfigs)
                )
        )
    }

    if (densityResourceConfigs.size > 1) {
        throw AaptException(
                "Cannot filter assets for multiple densities using "
                        + "SDK build tools 21 or later. Consider using apk splits instead."
        )
    }

    // if we are in split mode and resConfigs has been specified, we need to add all the
    // non density based splits back to the resConfigs otherwise they will be filtered out.
    if (!otherResourceConfigs.isEmpty() && config.splits != null) {
        val nonDensitySplits = AaptUtils.getNonDensityResConfigs(config.splits)
        otherResourceConfigs.addAll(Lists.newArrayList(nonDensitySplits))
    }

    if (preferredDensity == null && densityResourceConfigs.size == 1) {
        preferredDensity = Iterables.getOnlyElement(densityResourceConfigs)
    }

    if (!otherResourceConfigs.isEmpty()) {
        val joiner = Joiner.on(',')
        builder.add("-c", joiner.join(otherResourceConfigs))
    }

    if (preferredDensity != null) {
        builder.add("--preferred-density", preferredDensity)
    }

    if (config.symbolOutputDir != null && (config.variantType.isAar
            || !config.librarySymbolTableFiles.isEmpty())) {
        val rDotTxt = File(config.symbolOutputDir, "R.txt")
        builder.add("--output-text-symbols", rDotTxt.absolutePath)
    }

    if (config.packageId != null) {
        if (config.allowReservedPackageId) {
            builder.add("--allow-reserved-package-id")
        }
        builder.add("--package-id", "0x" + Integer.toHexString(config.packageId))
        for (dependentFeature in config.dependentFeatures) {
            builder.add("-I", dependentFeature.absolutePath)
        }
    } else if (!config.dependentFeatures.isEmpty()) {
        throw AaptException("Dependent features configured but no package ID was set.")
    }

    builder.add("--no-version-vectors")

    if (config.isStaticLibrary()) {
        builder.add("--static-lib")
        if (!config.staticLibraryDependencies.isEmpty()) {
            throw AaptException(
                    "Static libraries to link against should be passed as imports"
            )
        }
    } else {
        for (file in config.staticLibraryDependencies) {
            builder.add(file.absolutePath)
        }
    }

    return builder.build()
}